#include <xmlsec/crypto.h>
#include <xmlsec/openssl/crypto.h>
#include <assert.h>

#include "common.h"

static guint hashfunc(gconstpointer ptr) {
  xmlSecKeyDataPtr data = xmlSecKeyGetValue((xmlSecKeyPtr)ptr);

  if(xmlSecKeyDataCheckId(data, xmlSecOpenSSLKeyDataRsaId))
    return BN_mod_word(xmlSecOpenSSLKeyDataRsaGetRsa(data)->n, 0x100000);
  if(xmlSecKeyDataCheckId(data, xmlSecOpenSSLKeyDataDsaId))
    return BN_mod_word(xmlSecOpenSSLKeyDataDsaGetDsa(data)->pub_key, 0x100000);
  assert(0);
  return 0;
}

static gboolean equalfunc(gconstpointer a, gconstpointer b) {
  xmlSecKeyDataPtr da, db;

  da = xmlSecKeyGetValue((xmlSecKeyPtr)a);
  db = xmlSecKeyGetValue((xmlSecKeyPtr)b);

  assert(da);
  assert(db);
  if(da->id != db->id)
    return 0;
  if(xmlSecKeyDataCheckId(da, xmlSecOpenSSLKeyDataRsaId)) {
    RSA *rsaa, *rsab;

    rsaa = xmlSecOpenSSLKeyDataRsaGetRsa(da);
    rsab = xmlSecOpenSSLKeyDataRsaGetRsa(db);
    return !BN_cmp(rsaa->n, rsab->n) && !BN_cmp(rsaa->e, rsab->e);
  }
  if(xmlSecKeyDataCheckId(da, xmlSecOpenSSLKeyDataDsaId))
    return !BN_cmp(xmlSecOpenSSLKeyDataDsaGetDsa(da)->pub_key,
		   xmlSecOpenSSLKeyDataDsaGetDsa(db)->pub_key);
  assert(0);
  return 0;
}

static GHashTable *newKeyHash() {
  return g_hash_table_new(hashfunc, equalfunc);
}

struct trustState *newTrustState() {
  struct trustState *ret;

  ret = calloc(sizeof(*ret), 1);
  ret->map = newKeyHash();
  return ret;
}

void assertSigned(struct trustState *state, xmlSecKeyPtr signee,
		  xmlSecKeyPtr signer) {
  gpointer hash;
  unsigned char *se, *sr;

  se = BN_bn2dec(xmlSecOpenSSLKeyDataDsaGetDsa(xmlSecKeyGetValue(signee))->pub_key);
  sr = BN_bn2dec(xmlSecOpenSSLKeyDataDsaGetDsa(xmlSecKeyGetValue(signer))->pub_key);
  fprintf(stderr, "assertSigned:\n%s by\n%s\n", se, sr);
  OPENSSL_free(se);
  OPENSSL_free(sr);
  hash = g_hash_table_lookup(state->map, signer);
  if(!hash) {
    hash = newKeyHash();
    g_hash_table_insert(state->map, signer, hash);
  }
  g_hash_table_insert(hash, signee, (gpointer)-1);
}

struct searchState {
  GHashTable *trusted, *tmp;
  int found, success;
  struct trustState *state;
  xmlSecKeyPtr target;
};

static gboolean importHashTable(gpointer key, gpointer value, gpointer data) {
  GHashTable *t = data;

  g_hash_table_insert(t, key, value);
  return 1;
}

static void addTrusted(gpointer key, gpointer value, gpointer data) {
  struct searchState *ss = data;
  
  if(g_hash_table_lookup(ss->trusted, key))
    return;
  if(g_hash_table_lookup(ss->tmp, key))
    return;
  ss->found++;
  if(equalfunc(key, ss->target))
    ss->success = 1;
  g_hash_table_insert(ss->tmp, key, (gpointer)-1);
}

static void findTrusted(gpointer key, gpointer value, gpointer data) {
  GHashTable *t;
  struct searchState *ss = data;

  t = g_hash_table_lookup(ss->state->map, key);
  if(t)
    g_hash_table_foreach(t, addTrusted, ss);
}

static void cleanState(struct searchState *ss) {
  g_hash_table_destroy(ss->trusted);
  g_hash_table_destroy(ss->tmp);
  free(ss);
}

int findPath(struct trustState *state, xmlSecKeyPtr root, xmlSecKeyPtr key) {
  struct searchState *ss;

  ss = calloc(1, sizeof(*ss));
  ss->trusted = newKeyHash();
  ss->tmp = newKeyHash();
  ss->found = 1;
  ss->state = state;
  ss->target = key;
  g_hash_table_insert(ss->trusted, root, (gpointer)-1);
  while(ss->found) {
    ss->found = 0;
    g_hash_table_foreach(ss->trusted, findTrusted, ss);
    if(ss->success) {
      cleanState(ss);
      return 1;
    }
    g_hash_table_foreach_remove(ss->tmp, importHashTable, ss->trusted);
  }
  cleanState(ss);
  return 0;
}
